// Copyright 2017-2024, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ints

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/pulumi/pulumi/pkg/v3/testing/integration"
	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
	"github.com/pulumi/pulumi/sdk/v3/go/common/providers"
	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
)

func validateResources(t *testing.T, resources []apitype.ResourceV3, expectedNames ...string) {
	// Build the lookup table of expected resource names.
	expectedNamesTable := make(map[string]struct{})
	for _, n := range expectedNames {
		expectedNamesTable[n] = struct{}{}
	}

	// Pull out the stack resource, which must be the first resource in the checkpoint.
	stackRes, resources := resources[0], resources[1:]
	assert.Equal(t, resource.RootStackType, stackRes.URN.Type())

	// If there are more resources than just the stack, the second resource will be the default provider.
	if len(resources) > 0 {
		// Pull out the single provider resource, which should be the second resource in the checkpoint.
		providerRes := resources[0]
		resources = resources[1:]
		assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
	}

	// Ensure that the resource count is correct.
	assert.Equal(t, len(resources), len(expectedNames))

	// Ensure that exactly the provided resources are in the array.
	for _, res := range resources {
		name := res.URN.Name()
		_, ok := expectedNamesTable[name]
		assert.True(t, ok)
		delete(expectedNamesTable, name)
	}
}

// TestSteps tests many combinations of creates, updates, deletes, replacements, and so on.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestSteps(t *testing.T) {
	integration.ProgramTest(t, &integration.ProgramTestOptions{
		Dir:          "step1",
		Dependencies: []string{"@pulumi/pulumi"},
		Quick:        true,
		ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
			require.NotNil(t, stackInfo.Deployment)
			validateResources(t, stackInfo.Deployment.Resources, "a", "b", "c", "d")
		},
		EditDirs: []integration.EditDir{
			{
				Dir:      "step2",
				Additive: true,
				ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
					require.NotNil(t, stackInfo.Deployment)
					validateResources(t, stackInfo.Deployment.Resources, "a", "b", "c", "e")
				},
			},
			{
				Dir:      "step3",
				Additive: true,
				ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
					require.NotNil(t, stackInfo.Deployment)
					validateResources(t, stackInfo.Deployment.Resources, "a", "c", "e")
				},
			},
			{
				Dir:      "step4",
				Additive: true,
				ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
					require.NotNil(t, stackInfo.Deployment)
					validateResources(t, stackInfo.Deployment.Resources, "a", "c", "e")
				},
			},
			{
				Dir:      "step5",
				Additive: true,
				ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
					require.NotNil(t, stackInfo.Deployment)
					validateResources(t, stackInfo.Deployment.Resources, "a", "c", "e")
				},
			},
			{
				Dir:      "step6",
				Additive: true,
				ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
					require.NotNil(t, stackInfo.Deployment)
					validateResources(t, stackInfo.Deployment.Resources)
				},
			},
		},
	})
}
